package cn.waleychain.exchange.dfs.core;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.URL;

import com.jsuportframework.core.stream.JSIOUtils;
import com.jsuportframework.util.JSUtils;

import cn.waleychain.exchange.dfs.bean.FastdfsRemoteItem;
import cn.waleychain.exchange.dfs.bean.FastdfsUploadResult;
import cn.waleychain.exchange.dfs.client.ProtoCommon;
import cn.waleychain.exchange.dfs.client.StorageClient;
import cn.waleychain.exchange.dfs.client.TrackerClient;
import cn.waleychain.exchange.dfs.client.TrackerServer;
import cn.waleychain.exchange.dfs.client.UploadCallback;

/**
 * Fastdfs application.
 * 
 */
public class FastdfsApplication {

	/**
	 * Global fastdfs application.
	 */
	public static FastdfsApplication application = new FastdfsApplication();

	/**
	 * Global configuration.
	 */
	private FastdfsConfiguration globalConfiguration;

	/**
	 * Tracker client.
	 */
	private TrackerClient trackerClient;

	/**
	 * Tracker server.
	 */
	private TrackerServer trackerServer;

	/**
	 * Storage client.
	 */
	private StorageClient storageClient;

	/**
	 * Has been init.
	 */
	private boolean isInit;

	/**
	 * Constructs fastdfs instantce.
	 */
	private FastdfsApplication() {
		this.isInit = false;
	}

	/**
	 * Init current application with specified config path.
	 * 
	 * @param configPath
	 */
	public FastdfsApplication init(String configPath) {
		if (JSUtils.ifStringEmpty(configPath)) {
			throw new NullPointerException("Config path can not be null!");
		}
		if (isInit) {
			throw new FastdfsException("The current fastdfs server has been init completed!");
		}
		synchronized (this) {

			// Init configuraiton
			globalConfiguration = new FastdfsConfiguration(configPath);

			// Contructs TrackerClient.
			this.trackerClient = new TrackerClient();

			try {
				// Constructs tracker server.
				this.trackerServer = trackerClient.getConnection();
			} catch (Exception e) {
				throw new FastdfsException("Create tracker server failed, caused by: ", e);
			}

			// Contructs storage client.
			this.storageClient = new StorageClient(trackerServer, null);
			this.isInit = true;
		}
		return application;
	}

	/**
	 * Get global configuraiton.
	 * 
	 * @return
	 */
	public FastdfsConfiguration getGlobalConfiguration() {
		validateInit();
		return globalConfiguration;
	}

	/**
	 * Send ACTIVE_TEST command to server, test if network is ok and the server
	 * is alive
	 * 
	 * @return
	 * @throws IOException
	 */
	public boolean isActive() throws IOException {
		return ProtoCommon.activeTest(this.trackerServer.getSocket());
	}

	/**
	 * Upload file with specified local file name.
	 * 
	 * @param fileName
	 * @return
	 */
	public FastdfsUploadResult upload(String fileName) {
		return upload(new File(fileName));
	}

	/**
	 * Upload file to fastdfs storage server with specified local file.
	 * 
	 * @param uploadFile
	 * @return
	 */
	public FastdfsUploadResult upload(File uploadFile) {
		if (uploadFile == null) {
			throw new NullPointerException("Upload file can not be null!");
		}

		try {// Upload file.
			return upload(new FileInputStream(uploadFile), uploadFile.length(), uploadFile.getName());
		} catch (Exception e) {
			throw new FastdfsException("Upload file failed, caused by: ", e);
		}
	}

	/**
	 * Upload file to fastdfs storage server with specified url.
	 * 
	 * @param url
	 * @return
	 */
	public FastdfsUploadResult upload(URL url) {
		if (url == null) {
			throw new NullPointerException("URL can not be null!");
		}

		try {
			long length = url.openConnection().getContentLengthLong();
			return upload(url.openStream(), length, url.getFile());
		} catch (Exception e) {
			throw new FastdfsException("Upload file failed, caused by: ", e);
		}
	}

	/**
	 * Upload file to fastdfs storage server with specified input stream and
	 * file info.
	 * 
	 * @param input
	 * @param fileSize
	 * @param fileName
	 * @return
	 */
	public FastdfsUploadResult upload(InputStream input, long fileSize, String fileName) {
		if (input == null) {
			throw new NullPointerException("Input stream can not be null!");
		}
		if (fileSize <= 0) {
			throw new IllegalArgumentException("Illegal file size :" + fileSize);
		}
		validateInit();

		try {
			String extName = JSIOUtils.getFileExtension(fileName);
			// Upload file
			String[] results = this.storageClient.upload_file(null, fileSize, new FastdfsFileUpload(input), extName,
					null);
			if (results != null) {
				String host = FastdfsApplication.application.getGlobalConfiguration().getDFSUploadHost();
				// Build url.
				String url = (host.equals("/") ? "" : host) + "/" + results[0] + "/" + results[1];

				return new FastdfsUploadResult(fileName, results[0], results[1], url);
			} else {
				return new FastdfsUploadResult("Upload failed");
			}
		} catch (Exception e) {
			throw new FastdfsException("Upload file failed, caused by: ", e);
		}
	}

	/**
	 * Fastdfs file upload.
	 */
	private class FastdfsFileUpload implements UploadCallback, Serializable {
		/**
		 * serialVersionUID
		 */
		private static final long serialVersionUID = 7799657107337794186L;

		// Local file name.
		private InputStream inputStream;

		public FastdfsFileUpload(InputStream inputStream) {
			if (inputStream == null) {
				throw new NullPointerException("Input stream can not be null!");
			}
			this.inputStream = inputStream;
		}

		/**
		 * send file content callback function, be called only once when the
		 * file uploaded
		 * 
		 * @param out
		 *            output stream for writing file content
		 * @return 0 success, return none zero(errno) if fail
		 */
		public int send(OutputStream out) throws IOException {
			byte[] buff = new byte[256 * 1024]; // Read buffer
			int readBytes;

			try {
				while ((readBytes = inputStream.read(buff)) >= 0) {
					if (readBytes == 0) {
						continue;
					}

					out.write(buff, 0, readBytes);
				}
			} catch (Exception e) {
				e.printStackTrace();
				return -1;

			} finally {
				inputStream.close();
			}
			return 0;
		}
	}

	/**
	 * Delete file from storage server.
	 */
	public boolean delete(FastdfsRemoteItem result) {
		validateInit();

		try {
			int rs = this.storageClient.delete_file(result.getGroupName(), result.getRemoteFileName());
			return rs == 0;
		} catch (Exception e) {
			throw new FastdfsException("Delete file failed, caused by: ", e);
		}
	}

	/**
	 * Close server.
	 */
	public void close() {
		validateInit();

		if (trackerServer != null) {
			try {
				trackerServer.close();
				trackerServer = null;
			} catch (IOException e) {
				throw new FastdfsException("Close fastdfs server failed, caused by: ", e);
			}
			trackerServer = null;
		}
	}

	@Override
	protected void finalize() throws Throwable {
		close();
	}

	/**
	 * Validate is init.
	 */
	private void validateInit() {
		if (!isInit) {
			throw new FastdfsException("The current application is not init!");
		}
	}
}
